package hu.vissy.utils;

import com.google.common.base.Strings;

public class StringUtils {

    private StringUtils() {
    }


    public static class StringWrapperOptions {

        public static class Builder {
            private int width = 80;
            private int indent = 0;
            private int firstLineIndent = -1;
            private boolean justify = false;


            /**
             * @param width
             *            The maximal width of the wrapped text to set.
             */
            public Builder withWidth(int width) {
                this.width = width;
                return this;
            }

            /**
             * @param indent
             *            The indent level to set.
             */
            public Builder withIndent(int indent) {
                this.indent = indent;
                return this;
            }


            /**
             * @param firstLineIndent
             *            The indent level of the first line.
             */
            public Builder withFirstLineIndent(int firstLineIndent) {
                this.firstLineIndent = firstLineIndent;
                return this;
            }

            /**
             * @param justify
             *            the justify to set
             */
            public Builder withJustify(boolean justify) {
                this.justify = justify;
                return this;
            }

            public StringWrapperOptions getInstance() {
                if (width <= 0) {
                    throw new IllegalStateException("Width should be a positive number.");
                }
                if (indent < 0) {
                    throw new IllegalStateException("Indent should be a positive number.");
                }
                if (firstLineIndent == -1) {
                    firstLineIndent = indent;
                }
                if (firstLineIndent < 0) {
                    throw new IllegalStateException("First line indent should be a positive number.");
                }

                return new StringWrapperOptions(width, indent, firstLineIndent, justify);
            }
        }

        private int width = 80;
        private int indent = 0;
        private int firstLineIndent = 0;
        private boolean justify = false;

        private StringWrapperOptions(int width, int indent, int firstLineIndent, boolean justify) {
            super();
            this.width = width;
            this.indent = indent;
            this.firstLineIndent = firstLineIndent;
            this.justify = justify;
        }

        /**
         * @return The maximal width of the wrapped text.
         */
        public int getWidth() {
            return width;
        }

        /**
         * @return The indent level.
         */
        public int getIndent() {
            return indent;
        }

        /**
         * @return The indent level of the first line.
         */
        public int getFirstLineIndent() {
            return firstLineIndent;
        }

        /**
         * @return the justify
         */
        public boolean isJustify() {
            return justify;
        }
    }





    /**
     * Wraps the text into the specified width.
     *
     * <p>
     * This is identical with the
     * <code>wrapString(desc, width, 0, 0, false)</code> call.
     * </p>
     *
     * @param desc
     *            The text to be wrapped.
     * @param width
     *            The width of the wrapping.
     * @return The wrapped text.
     * @see #wrapString(String, int, int, int, boolean)
     */
    public static String wrapString(String desc, int width) {
        return wrapString(desc, new StringWrapperOptions.Builder().withWidth(width).getInstance());
    }


    /**
     * Wraps the text into the specified width with full justification,
     * indentation and different indentation for the first line (hanging
     * paragraph).
     *
     * <p>
     * Note, that the only way the wrapped text extends over specified width is
     * when there is a single word with greater length than the wrapping width
     * (the width reduced by indentation).
     * </p>
     *
     * @param desc
     *            The text to be wrapped.
     * @param options
     *            The options of the wrapping.
     * @return The wrapped text.
     * @see #wrapString(String, int, int, int, boolean)
     */
    public static String wrapString(String desc, StringWrapperOptions options) {
        StringBuilder sb = new StringBuilder();
        desc += " ";

        // Performance optimization: creating indent and first line string.
        String indentStr = Strings.repeat(" ", options.getIndent());
        String fistrLineStr = Strings.repeat(" ", options.getFirstLineIndent());

        desc = desc.replaceAll("\r", "").replaceAll("\n", " ").replaceAll("\t", "\b\b\b\b");

        int w = options.getWidth() - options.getIndent();
        boolean isFirstLine = true;
        while (desc.length() != 0) {
            int p;

            // Add indentation
            if (isFirstLine) {
                sb.append(fistrLineStr);
            } else {
                sb.append(indentStr);
            }
            isFirstLine = false;


            StringBuilder line;
            int q = desc.indexOf("%%");
            if (q != -1 && (q < w || desc.indexOf(' ') == -1 || desc.indexOf(' ') > q)) {
                line = new StringBuilder(desc.substring(0, q));
                p = q + 2;
                isFirstLine = true;
            } else {
                p = Math.min(w, desc.length() - 1);
                while (p >= 0 && desc.charAt(p) != ' ') {
                    p--;
                }
                if (p == -1) {
                    p = Math.min(w, desc.length() - 1);
                    while (p < desc.length() && desc.charAt(p) != ' ') {
                        p++;
                    }
                }
                line = new StringBuilder(desc.substring(0, p).trim());
                if (options.isJustify()) {
                    int spCnt = 0;
                    for (int i = 1; i < line.length(); i++) {
                        if (line.charAt(i) == ' ') {
                            if (line.charAt(i - 1) == ' ') {
                                continue;
                            }
                            spCnt++;
                        }
                    }
                    if (spCnt > 0) {
                        while (line.length() < w) {
                            int delta = w - line.length();
                            if (delta >= spCnt) {
                                int i = line.length() - 2;
                                while (i >= 0) {
                                    if (line.charAt(i) == ' ' && line.charAt(i + 1) != ' ') {
                                        line.insert(i, ' ');
                                    }
                                    i--;
                                }
                            } else {
                                // Not the best, but works
                                int[] sp = MathUtils.deal(spCnt, delta);
                                int i = line.length() - 2;
                                int spc = spCnt - 1;
                                int spp = delta - 1;
                                while (i >= 0) {
                                    if (line.charAt(i) == ' ' && line.charAt(i + 1) != ' ') {
                                        if (spc == sp[spp]) {
                                            line.insert(i, ' ');
                                            spp--;
                                            if (spp == -1) {
                                                break;
                                            }
                                        }
                                        spc--;
                                    }
                                    i--;
                                }
                            }
                        }

                    }
                }
            }


            sb.append(line.toString().replaceAll("\b", " ")).append("\n");
            while (p < desc.length() && (desc.charAt(p) == ' ' || desc.charAt(p) == '\n')) {
                p++;
            }
            desc = desc.substring(p);
            while (desc.startsWith("%%")) {
                sb.append("\n");
                p = 2;
                while (p < desc.length() && desc.charAt(p) == ' ') {
                    p++;
                }
                desc = desc.substring(p);
            }
        }
        return sb.toString();
    }




}
